package cn._2dland.http.method;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import cn._2dland.http.Cookie;
import cn._2dland.http.HttpClientParams;
import cn._2dland.http.HttpHeader;
import cn._2dland.http.NameValuePair;

public abstract class HttpMethod {

	/** HTTP连接 */
	protected HttpURLConnection conn;

	/** 请求设置 */
	protected HttpClientParams params = new HttpClientParams();

	/** 请求URL */
	protected String url = null;
	/** 请求头 */
	protected Map<String, String> requestHeaders = null;
	/** 请求参数表 */
	protected Map<String, String> parameters = null;

	/** 响应头 */
	protected Map<String, String> responseHeaders = null;
	/** 响应编码 */
	protected String serverCharset = "utf-8";
	/** 响应Cookie */
	protected Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();

	public HttpMethod(){}
	public HttpMethod(String url){
		this.url = url;
	}

	/**
	 * 获取方法参数
	 * 
	 * @return
	 */
	public HttpClientParams getMethodParams() {
		return params;
	}

	/**
	 * 获取方法名称
	 * 
	 * @return
	 */
	abstract public String getMethodName();

	/** 
	 * 设置URL地址
	 * 
	 * @param url
	 */
	public void setURL(String url) {
		this.url = url;
	}

	/**
	 * 获取URL地址
	 * 
	 * @return
	 */
	public String getURL() {
		return this.url;
	}

	/**
	 * 获取要查询的内容
	 * 
	 * @return
	 */
	protected String getQueryString() {
		return null;
	}

	/**
	 * 获取要提交的数据
	 * 
	 * @return
	 */
	protected byte[] getData() {
		return null;
	}

	/**
	 * 添加请求头
	 * 
	 * @param name 头名称
	 * @param value 头内容
	 */
	public void addRequestHeader(String name, String value) {
		if(requestHeaders == null)
			requestHeaders = new HashMap<String, String>();
		requestHeaders.put(name, value);
	}

	/**
	 * 添加一组请求头
	 * 
	 * @param headers
	 */
	public void addRequestHeaders(NameValuePair[] headers) {
		if(headers == null)
			return;
		for(NameValuePair pair : headers)
			addRequestHeader(pair.name, pair.value);
	}

	/**
	 * 添加请求参数
	 * 
	 * @param name
	 * @param value
	 */
	public void addParameter(String name, String value) {
		if(parameters == null)
			parameters = new HashMap<String, String>();
		parameters.put(name, value);
	}

	/**
	 * 添加一组请求参数
	 * 
	 * @param params
	 */
	public void addParameters(NameValuePair[] params) {
		if(params == null)
			return;
		for(NameValuePair pair : params)
			addParameter(pair.name, pair.value);
	}

	/**
	 * 获取请求参数表
	 * 
	 * @return
	 */
	public Map<String, String> getParameters() {
		return this.parameters;
	}

	/**
	 * 执行方法
	 * 
	 * @return
	 */
	public int execute() {
		int respCode = 0;
		try {
			// 构造请求地址
			StringBuffer urlBuf = new StringBuffer(getURL());
			String queryString = getQueryString();
			if(queryString != null) {
				urlBuf.append(urlBuf.indexOf("?") < 0 ? "?" : "&").append(queryString);
			}

			// 建立HTTP连接
			URL u = new URL(urlBuf.toString());
			conn = (HttpURLConnection) u.openConnection();
			conn.setRequestMethod(getMethodName());
			conn.setDoInput(true);
			conn.setDoOutput(true);
			conn.setInstanceFollowRedirects(params.isFollowRedirect());
			conn.setUseCaches(params.isUseCaches());

			// 设置请求头
			conn.setRequestProperty("User-Agent", params.getUserAgent());
			if(requestHeaders != null) {
				for(Entry<String, String> header : requestHeaders.entrySet()) {
					conn.setRequestProperty(header.getKey(), header.getValue());
				}
			}
			// 发起连接
			conn.connect();

			// 写入数据
			byte[] data = getData();
			if(data != null) {
				OutputStream os = conn.getOutputStream();
				os.write(data);
				os.flush();
			}

			// 执行连接动作
			respCode = conn.getResponseCode();

			// 保存响应头
			responseHeaders = new HashMap<String, String>();
			for(Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
				if(entry.getKey() == null)
					continue;

				String headerName = entry.getKey();
				if(HttpHeader.HEADER_SET_COOKIE.equals(headerName)) {
					// set-cookie头需要特殊处理
					saveCookie(entry.getValue());
				} else {
					responseHeaders.put(headerName.toLowerCase(), entry.getValue().get(0));
				}
			}

			// 获取响应信息的编码
			String contentType = conn.getContentType();
			if(contentType != null && contentType.matches("^.*charset=[\\w\\-]+.*$")) {
				Matcher m = Pattern.compile("charset=[\\w\\-]+").matcher(contentType);
				while(m.find())
					serverCharset = contentType.substring(m.start() + 8, m.end());
			} else {
				serverCharset = "utf-8";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return respCode;
	}

	/**
	 * 获取响应头
	 * 
	 * @param header 头名称
	 * @return
	 */
	public String getResponseHeader(String header) {
		return responseHeaders.get(header.toLowerCase());
	}

	/** 获取Cookie */
	public List<Cookie> getCookies() {
		List<Cookie> cookies = new ArrayList<Cookie>(cookieMap.values());
		return cookies;
	}

	/** 获取响应流 */
	public InputStream getResponseStream() {
		InputStream stream = null;
		try{ stream = conn.getInputStream(); }
		catch (Exception e) { }
		return stream;
	}

	/** 获取响应文本 */
	public String getResponseString() {
		String body = null;
		try {
			// 获取结果流
			InputStream is = conn.getInputStream();
			// 判断是否需要解压
			String enc = responseHeaders.get(HttpHeader.HEADER_CONTENT_ENCODING);
			if(enc != null && enc.indexOf("gzip")>=0)
				is = new GZIPInputStream(is);

			// 读取结果流，并构建字符串
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			for(int b; (b=is.read()) >= 0; )
				baos.write(b);
			baos.flush();
			body = new String(baos.toByteArray(), serverCharset);

			// 释放资源
			baos.close();
			is.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return body;
	}

	/**
	 * 释放连接
	 */
	public void releaseConnection() {
		conn.disconnect();
	}

	/** 保存Cookie */
	private void saveCookie(List<String> rawCookie) {
		cookieMap.clear();
		// fuck URL Fetch!
		// cookie过期标识中的逗号被当作了分隔符，若包含该标识，则需要与后一个cookie合并
		for(int i=0; rawCookie!=null && i<rawCookie.size(); i++) {
			String s = rawCookie.get(i);
			// 如果cookie中包含过期标识，则需要与下一段合并
			if(s.matches("^.*; expires=.*$"))
				s += ", " + rawCookie.get(++i);
			Cookie c = Cookie.parse(s);
			if(c == null) continue;
			cookieMap.put(c.name, c);
		}
	}
}
